home *** CD-ROM | disk | FTP | other *** search
/ MacTech 1 to 12 / MacTech-vol-1-12.toast / Source / MacTech® Magazine / Volume 07 - 1991 / 07.01 Jan 91 / FlexAlert / FlexAlert Code / FLexAlert.c next >
Encoding:
C/C++ Source or Header  |  1990-04-10  |  14.3 KB  |  458 lines  |  [TEXT/KAHL]

  1. /* FlexAlert.c - flexible alert routine.
  2.  - sizes alert window to fit the text
  3.  - automatic screen positioning
  4.  - choice of buttons:justOK, okCancel, cancelOK
  5.  - choice of icons, or no icon
  6. Compiler: Think C 3 or 4. Note if you're not having
  7. the <MacHeaders> file included automatically this file
  8. requires the same standard includes necessary to call
  9. an alert in the standard way.
  10. *******************************
  11. IMPORTANT ASSUMPTION: that you have called TEInit() at startup
  12. but then you knew that didn't you?
  13. RESTRICTIONS ON USAGE (ie tiny bugs):
  14. DO NOT USE for out-of-memory alerts, as FlexAlert()
  15.     uses a bit of memory temporarily.
  16. Draws to the main screen only.
  17. Clipping of the text message will occur;
  18.     - with no carriage returns in the message, at
  19.     about 850 chars with no icon, 750 with an icon
  20.     - sooner if you have carriage returns; with
  21.     STDTOP = 100 you get 12 lines max.
  22. FlexAlert() requires 3 ALRT's and 3 DITL's in resources (see below).
  23. **********************************/
  24.  
  25. #include "FlexAlert.h"
  26.  
  27. /* #include <string.h> if you use strlen()
  28.     rather than stringLength() - see below.*/
  29.  
  30. #ifndef nil
  31. #define        nil        0L
  32. #endif
  33.  
  34. /* Change these three numbers to match the 3 ALRT's and DITL's you create.
  35. You'll find instructions on creating them in the text above, or if you're
  36. looking at a source code disk you'll find them in the file
  37. FlexAlert.rsrc */
  38. #define        JUST_OKALERTID        274    /* OK button as item (1), stat text as item (2) */
  39. #define        OK_CANALERTID        272    /* OK(1), Cancel(2), stat text(3) */
  40. #define        CAN_OKALERTID        273    /* Cancel(1), OK(2), stat text(3) */
  41.  
  42. #define        STDTOP        100    /* top of alert on small screen */
  43. #define        ALRTTOP        STDTOP    /* the old-fashioned way */
  44. /*
  45. or, if you are a stickler about user interface guidelines, you
  46. will want one-third of the grey area above the alert, roughly:
  47. #define        TOPADJUST    (342 - 3*STDTOP)
  48. #define        SCRNH        screenBits.bounds.bottom - screenBits.bounds.top
  49. #define        ALRTTOP        (SCRNH - TOPADJUST)/3
  50. This is a compromise, to avoid alerts bouncing up and down the screen.
  51. */
  52. #define        STDW        512 /* standard small screen width */
  53. #define        STDH        (342 + ALRTTOP - STDTOP)    /* adjusted small screen height */
  54. #define        LEFTINSET    5    /* gap between text and window edge */
  55. #define        TOPINSET    5
  56. #define        ICONPIX        52    /* horizontal pixels needed for icon */
  57. #define        BUTH        16    /* height of OK, Cancel buttons */
  58. #define        BUTW        60    /* width of buttons */
  59. #define        BUTGAP        20    /* space between OK and Cancel */
  60. #define        MAXALERTTEXT    1020L /* characters in alert - four pascal strings */
  61.  
  62. static char *NilAlertString = "\P";
  63.  
  64. /* Functions defined in this file - FlexAlert() is the one to call. */
  65. /*int FlexAlert(int buttonMode, int whichIcon, Ptr csPtr);*/
  66. /* support functions */
  67. static void GetTextSize(int maxTextW, int maxTextH,int *statWPtr, int *statHPtr, Ptr csPtr);
  68. static void SetParamText(Ptr csPtr, Ptr paramPtr);
  69. /* general utilities - you may want to roll your own */
  70. int GetNumLines(TEHandle theTE); /* (**TEH).nLines isn't always right */
  71. long stringLength(char *s); /* just a version of strlen() */
  72.  
  73. /* FlexAlert(); calls one of three alerts, sizing alert to fit the text.
  74. buttonMode:0 = justOK (just an OK button)
  75.     1 = okCancel (OK and Cancel buttons, default is OK)
  76.     2 = cancelOK (default is Cancel)
  77. whichIcon: 0,1,2 = stopIcon, noteIcon, cautionIcon respectively,
  78.     any other value means no icon
  79. csPtr is a pointer to a C string, absolute maximium of 1020 characters.
  80. Returns number of button hit (1 or 2, 1 being default) or 0 if error,
  81.     or -1 if the alert stage was not drawn.
  82. Note that a returned value of 1 means OK if buttonMode is okCancel,
  83. and means Cancel if buttonMode is cancelOK.
  84. The alert is drawn 'ALRTTOP' pixels down from the top of the screen,
  85. and is centered left-right. The size of the alert is restricted so
  86. that it will always fit on a Mac Plus screen, allowing you to check that
  87. text is not clipped when using any size of main screen.
  88. Typical call;
  89. static char *stopMessage = "Are you seeing a stop alert with \
  90. an OK button as default, and also a Cancel button?";
  91. int buttonHit;
  92. if (!(buttonHit = FlexAlert(okCancel, stopIcon, stopMessage)))
  93.     - alert could not be shown due to an error of some sort;
  94. else if (buttonHit == 1) - the default (OK);
  95. else if (buttonHit == 2) - the other button (here Cancel);
  96. else if (buttonHit == -1) - the alert was not drawn; if you
  97. set the alerts to be drawn all 4 stages this won't happen.
  98. **************************************/
  99. int FlexAlert(int buttonMode, int whichIcon, Ptr csPtr)
  100.     {
  101.     int            resID;
  102.     Rect        *drp, *rPtr;
  103.     AlertTHndl    alrtHandle;
  104.     Handle        itemH, paramH;
  105.     int            i, tempOff, totalItems, numButtons,
  106.                 w, hitItem, actualW,
  107.                 maxTextH, maxTextW, alrtLeft, alrtTop,
  108.                 statW, statH;
  109.     Boolean     hasIcon;
  110.     
  111.     actualW = screenBits.bounds.right - screenBits.bounds.left;
  112.     alrtTop = ALRTTOP; /* nominally 100 */
  113.     
  114.     if (buttonMode == okCancel)
  115.         {
  116.         resID = OK_CANALERTID;
  117.         numButtons = 2;
  118.         }
  119.     else if (buttonMode == cancelOK)
  120.         {
  121.         resID = CAN_OKALERTID;
  122.         numButtons = 2;
  123.         }
  124.     else /* justOK by default */
  125.         {
  126.         resID = JUST_OKALERTID;
  127.         numButtons = 1;
  128.         }
  129.     /* Load ALRT and DITL for adjustment before showing */
  130.     if (!(alrtHandle = (AlertTHndl)GetResource('ALRT', resID))
  131.         || !(itemH = GetResource('DITL', resID))
  132.         || ResError())
  133.         return(0);
  134.     
  135.     if (whichIcon == stopIcon || whichIcon == noteIcon
  136.         || whichIcon == cautionIcon)
  137.         hasIcon = TRUE;
  138.     else
  139.         hasIcon = FALSE;
  140.     
  141.     /* Determine maximum room available for stat text box in alert. */
  142.     /* restrict maximum size to fit on Mac Plus screen */
  143.     /* horizontal layout: gap, (icon), alert left, 
  144.         gap, text, gap, alert right, gap */
  145.     if (!hasIcon)
  146.         maxTextW = STDW - 4*LEFTINSET;
  147.     else
  148.         maxTextW = STDW - 4*LEFTINSET - ICONPIX;
  149.     /* vertical layout: down to ALRTTOP, followed by
  150.     alert top, gap, text, two gaps, buttons, gap, alert bottom, gap */
  151.     maxTextH = STDH - alrtTop - 5*TOPINSET - BUTH;
  152.     
  153.     /* If no room for text, return error */
  154.     if (maxTextW <= 0 || maxTextH <= 0)
  155.         return(0);
  156.     
  157.     /* Determine statW, statH, the width and height of
  158.     the static text box. */
  159.     GetTextSize(maxTextW, maxTextH, &statW, &statH, csPtr);
  160.     if (statW <= 0 || statH <= 0)
  161.         return(0);
  162.     
  163.     /* Given autoPlaceIt, statW, statH, hasIcon, set alrt window
  164.     position and size. */
  165.     drp = &((**alrtHandle).boundsRect);
  166.     if (!hasIcon)
  167.         w = statW + 2*LEFTINSET;
  168.     else
  169.         w = statW + 2*LEFTINSET + ICONPIX;
  170.     drp->left = (actualW - w)/2;
  171.     drp->right = drp->left + w;
  172.     drp->top = alrtTop;
  173.     drp->bottom = alrtTop + statH + 4*TOPINSET + BUTH;
  174.     
  175.     /* Adjust rects for buttons and stat text in alert.
  176.     See eg MacTutor September 89 pg 83 for DITL details. */
  177.     tempOff = 6;
  178.     totalItems = *((int *)(*itemH)) + 1;
  179.     for (i = 0; i < totalItems; ++i)
  180.         {
  181.         rPtr = (Rect *)(*itemH + tempOff);
  182.         if (i == 0) /* first button, always present */
  183.             {
  184.             rPtr->top = drp->bottom - drp->top - TOPINSET - BUTH;
  185.             if (numButtons == 2)
  186.                 w = (drp->right - drp->left - BUTGAP - 2*BUTW)/2
  187.                     + LEFTINSET;
  188.             else
  189.                 w = (drp->right - drp->left - BUTW)/2;
  190.             rPtr->left = w;
  191.             rPtr->bottom = rPtr->top + BUTH;
  192.             rPtr->right = w + BUTW;
  193.             }
  194.         else if (i == 1 && numButtons == 2) /* second button if present */
  195.             {
  196.             rPtr->top = drp->bottom - drp->top - TOPINSET - BUTH;
  197.             rPtr->left = w + BUTW + BUTGAP; /* w was set in i == 0 */
  198.             rPtr->bottom = rPtr->top + BUTH;
  199.             rPtr->right = rPtr->left + BUTW;
  200.             }
  201.         else /* stat text by default, i = 1 or 2 */
  202.             {
  203.             rPtr->top = TOPINSET;
  204.             if (!hasIcon)
  205.                 rPtr->left = LEFTINSET;
  206.             else
  207.                 rPtr->left = LEFTINSET + ICONPIX;
  208.             rPtr->bottom = TOPINSET + statH + 3;
  209.             rPtr->right = rPtr->left + statW;
  210.             }
  211.         tempOff += 8;
  212.         tempOff += 1;
  213.         tempOff += (int)(*((Byte *)(*itemH + tempOff))) + 1;
  214.         if (tempOff & 1)
  215.             ++tempOff;
  216.         tempOff += 4;
  217.         }
  218.     
  219.     /* Set param text and call alert. */
  220.     paramH = NewHandle(stringLength(csPtr) + 4);
  221.     if (MemError() != noErr)
  222.         return(0);
  223.     HLock(paramH);
  224.     SetParamText(csPtr, *paramH);
  225.     
  226.     if (whichIcon == stopIcon)
  227.         hitItem = StopAlert(resID, nil);
  228.     else if (whichIcon == noteIcon)
  229.         hitItem = NoteAlert(resID, nil);
  230.     else if (whichIcon == cautionIcon)
  231.         hitItem = CautionAlert(resID, nil);
  232.     else
  233.         hitItem = Alert(resID, nil);
  234.     HUnlock(paramH);
  235.     DisposHandle(paramH);
  236.     return(hitItem);
  237.     }
  238.  
  239. /* If you take away from the smallest Mac screen the little bits of it needed
  240. for the alert buttons, icon, separating gaps, and the menu bar, you have left
  241. the maximum area of screen available on any Mac for placing the alert text, passed to
  242. this function in maxTextW, maxTextH. GetTextSize() calculates a nice minimal
  243. rectangle in which to pour the alert text, returning the width and height
  244. in statWPtr, statHPtr. After this function is called, the "little bits" are
  245. added back around the text box when placing the alert and its buttons, icon etc
  246. on the screen. The result of this approach is that GetTextSize() is relatively
  247. general and simple - with a bit of "fine tuning" you could probably use it for
  248. other purposes. Fiddly note: add 3 or so pixels to the height returned by this
  249. function to prevent the last line of text being clipped.
  250. *************************************/
  251. static void GetTextSize(int maxTextW, int maxTextH,int *statWPtr, int *statHPtr, Ptr csPtr)
  252.     {
  253.     GrafPtr        savePort;
  254.     Handle        dumH,textH;
  255.     Rect        dumR;
  256.     WindowPtr    dumW;
  257.     TEHandle    dumTE;
  258.     FontInfo    tempInfo;
  259.     long        textLen;
  260.     int            lineHeight, statW, statH, curH;
  261.     Boolean        foundGoodSize;
  262.     
  263.     SetRect(&dumR, 100,100,200,200);
  264.     /* Create a dummy window, never shown on-screen. */
  265.     GetPort(&savePort);
  266.     dumW = NewWindow(nil,&dumR,"\P",FALSE,0,-1L,FALSE,0L);
  267.     SetPort(dumW);
  268.     /* Copy text into a handle for use with a TEHandle. */
  269.     textLen = stringLength(csPtr);
  270.     if (textLen > MAXALERTTEXT)
  271.         textLen = MAXALERTTEXT;
  272.     textH = NewHandle(textLen);
  273.     if (MemError() != noErr)
  274.         { /* pretend nothing bad happened */
  275.         *statWPtr = maxTextW;
  276.         *statHPtr = maxTextH;
  277.         DisposeWindow(dumW);
  278.         SetPort(savePort);
  279.         return;
  280.         }
  281.     BlockMove(csPtr, *textH, textLen);
  282.     TextFont(0);
  283.     TextSize(0);
  284.     TextFace(0);
  285.     GetFontInfo(&tempInfo);
  286.     lineHeight = tempInfo.ascent + tempInfo.descent + tempInfo.leading;
  287.     /* trim max height to an integral number of lines */
  288.     maxTextH = (maxTextH/lineHeight)*lineHeight;
  289.     /* start with smallest reasonable alert and work up */
  290.     statH = 6*lineHeight; /* about 72 */
  291.     statW = 12*lineHeight; /* about 144 */
  292.     if (statW >= maxTextW)
  293.         statW = maxTextW;
  294.     if (statH >= maxTextH)
  295.         {
  296.         statW = maxTextW;
  297.         statH = maxTextH;
  298.         }
  299.     /* Do the "drawing" of text in the next building */
  300.     dumR.top = 20000;
  301.     dumR.left = 20000;
  302.     dumR.right = dumR.left + statW;
  303.     dumR.bottom = dumR.top + 100;
  304.     dumTE = TENew(&dumR, &dumR);
  305.     /* An instance of what I like to call
  306.     "defensive stupidity" here follows: */
  307.     if ((**dumTE).hText)
  308.         { /* I don't know and I don't care */
  309.         dumH = (**dumTE).hText;
  310.         DisposHandle(dumH);
  311.         }
  312.     (**dumTE).hText = textH;
  313.     (**dumTE).teLength = textLen;
  314.     
  315.     /* For your viewing entertainment you might like
  316.     to play with the algorithm in the following while
  317.     loop: the goal is to experimentally draw the text in
  318.     a variety of rectangles until a small rectangle with
  319.     a pleasing shape is found. The approach at present is;
  320.     start with a box 6 lines high and grow it a line
  321.     at a time until the text fits. The width is the minimum
  322.     of two times the height versus the maximum width
  323.     available. For clarity a 'last resort' loop is shown
  324.     separately - the 2:1 apect ratio is abandoned.
  325.     Text will be clipped if we run off the
  326.     bottom, typically at over 750 characters.
  327.     */
  328.     foundGoodSize = FALSE;
  329.     while (!foundGoodSize)
  330.         {
  331.         (**dumTE).viewRect.right = 20000 + statW;
  332.         (**dumTE).destRect.right = 20000 + statW;
  333.         TECalText(dumTE);
  334.         curH = GetNumLines(dumTE)*lineHeight;
  335.         if (curH <= statH)
  336.             {
  337.             statH = curH;
  338.             foundGoodSize = TRUE;
  339.             }
  340.         else /* try again */
  341.             {
  342.             statH += lineHeight;
  343.             if (statH >= maxTextH)
  344.                 { /* running off the bottom */
  345.                 statH = maxTextH;
  346.                 foundGoodSize = TRUE;/* well, half-true */
  347.                 }
  348.             statW = statH*2;
  349.             if (statW >= maxTextW)
  350.                 statW = maxTextW;
  351.             }
  352.         }
  353.     /* A 'last resort' attempt; if there is room to grow
  354.     to the right, use it, at the expense of wrecking the
  355.     2:1 aspect ratio of the text box. */
  356.     if (curH > maxTextH && statW < maxTextW)
  357.         {
  358.         foundGoodSize = FALSE;
  359.         while (!foundGoodSize)
  360.             {
  361.             statW += 3*lineHeight;
  362.             if (statW >= maxTextW)
  363.                 {
  364.                 statW = maxTextW;
  365.                 break; /* ie give up */
  366.                 }
  367.             else
  368.                 {
  369.                 (**dumTE).viewRect.right = 20000 + statW;
  370.                 (**dumTE).destRect.right = 20000 + statW;
  371.                 TECalText(dumTE);
  372.                 curH = GetNumLines(dumTE)*lineHeight;
  373.                 if (curH <= statH)
  374.                     {
  375.                     statH = curH;
  376.                     foundGoodSize = TRUE;
  377.                     }
  378.                 }
  379.             } /* while */
  380.         }
  381.     /* Clean up, and return the best values found */
  382.     *statWPtr = statW;
  383.     *statHPtr = statH;
  384.     TEDispose(dumTE);
  385.     DisposeWindow(dumW);
  386.     SetPort(savePort);
  387.     }
  388.  
  389. /* csPtr is a c-string; paramPtr is a pointer to a locked handle which will
  390. hold the four paramtext strings (pascal format) and must be properly
  391. sized beforehand. The ParamText pointers are progressively set up and text
  392. is poured from the c-string into the locked handle as consecutive pascal
  393. strings, with any unused ParamText pointers left pointing to a zero-length string.
  394. *****************************/
  395. static void SetParamText(Ptr csPtr, Ptr paramPtr)
  396.     {
  397.     Ptr            p[4];
  398.     long        csLen;
  399.     int         curParamLen, csPos, i;
  400.     
  401.     p[0] = p[1] = p[2] = p[3] = NilAlertString;
  402.     csLen = stringLength(csPtr);
  403.     if (csLen > MAXALERTTEXT) /* 1020 absolute maximum */
  404.         csLen = MAXALERTTEXT;
  405.     /* csPos is index into csPtr, range 0:csLen-1 */
  406.     csPos = 0;
  407.     /* curParamLen indexes a particular pascal string
  408.     after the len byte, range 0:254 */
  409.     curParamLen = 0;
  410.     i = 0; /* index into p[], range 0:3 */
  411.     while (i < 4 && csPos < csLen)
  412.         {
  413.         p[i] = paramPtr + csPos + i;
  414.         while (curParamLen < 255 && csPos < csLen)
  415.             {
  416.             *(p[i] + curParamLen + 1) = *(csPtr + csPos);
  417.             ++csPos;
  418.             ++curParamLen;
  419.             }
  420.         *(p[i]) = curParamLen;
  421.         ++i;
  422.         curParamLen = 0;
  423.         }
  424.     ParamText(p[0],p[1],p[2],p[3]);
  425.     }
  426.  
  427. /* This function returns at least 1 no matter what, and
  428. corrects (**TEH).nLines if the last character is a Return.
  429. ******************************/
  430. int GetNumLines(TEHandle TEH)
  431.     {
  432.     Handle    hText;
  433.     long    lastChar;
  434.     int        nLines;
  435.     
  436.     nLines = (**TEH).nLines;
  437.     hText = TEGetText(TEH);
  438.     lastChar = GetHandleSize(hText) - 1L;
  439.     if (lastChar < 0) return(1);
  440.     if (*(*hText + lastChar) == 0x0D) /* a Return */
  441.         ++nLines;
  442.     if (nLines <= 0)
  443.         nLines = 1;
  444.     return(nLines);
  445.     }
  446.  
  447. /* If you are willing to #include <string.h> you can replace the
  448. three calls above to stringLength() with strlen().
  449. *****************************/
  450. long stringLength(char *s)
  451.     {
  452.     long        len = 0L;
  453.     
  454.     while (*s++)
  455.         ++len;
  456.     return(len);
  457.     }
  458.